﻿using System;
using System.Collections.Generic;
using System.IO.MemoryMappedFiles;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace Sharp.Interprocess.MemoryManager
{
    internal static class MemoryManagerUtil
    {
        private const int MIN_POWER_OF_2 = 2;
        private const int MAX_POWER_OF_2 = 10;

        internal static int[] SegmentSizes { get; private set; }

        internal static Dictionary<int, int> PowerOf2ByResult; //lookup table to calculate k by using as key result of 2^k
        internal static Dictionary<int, int> ResultByPowerOf2;

        internal static int MinSegmentSize { get; private set; }
        internal static int MaxSegmentSize { get; private set; }

        static MemoryManagerUtil()
        {
            List<int> memoryBlockSizes = new List<int>();
            PowerOf2ByResult = new Dictionary<int, int>();
            ResultByPowerOf2 = new Dictionary<int, int>();
            for (double powerOf2 = MIN_POWER_OF_2; powerOf2 <= MAX_POWER_OF_2; powerOf2++)
            {
                var powerOf2Result = Convert.ToInt32(Math.Pow(2, powerOf2));
                if (powerOf2 == MIN_POWER_OF_2)
                {
                    MinSegmentSize = powerOf2Result;
                }

                if (powerOf2 == MAX_POWER_OF_2)
                {
                    MaxSegmentSize = powerOf2Result;
                }

                memoryBlockSizes.Add(powerOf2Result);
                PowerOf2ByResult.Add(Convert.ToInt32(powerOf2Result), Convert.ToInt32(powerOf2));
                ResultByPowerOf2.Add(Convert.ToInt32(powerOf2), Convert.ToInt32(powerOf2Result));
            }

            SegmentSizes = memoryBlockSizes.ToArray();
        }

        internal static TData ReadData<TData>(this MemoryMappedFile memoryMappedFile, int offset)
            where TData : struct
        {
            TData fetchedData;

            int dataSize = Marshal.SizeOf(typeof(TData));

            using (var viewAccessor = memoryMappedFile.CreateViewAccessor(offset, dataSize))
            {
                viewAccessor.Read<TData>(0, out fetchedData);
            }

            return fetchedData;
        }

        internal static void WriteData<TData>(this MemoryMappedFile memoryMappedFile, int offset, TData dataToWrite)
            where TData : struct
        {
            int dataSize = Marshal.SizeOf(typeof(TData));

            using (var viewAccessor = memoryMappedFile.CreateViewAccessor(offset, dataSize))
            {
                viewAccessor.Write<TData>(0, ref dataToWrite);
            }
        }

        internal static TData[] ReadDataArray<TData>(this MemoryMappedFile memoryMappedFile, int offset, int arrayLength)
                   where TData : struct
        {
            TData[] fetchedData = new TData[arrayLength];

            int dataSize = Marshal.SizeOf(typeof(TData));
            using (var viewAccessor = memoryMappedFile.CreateViewAccessor(offset, dataSize * arrayLength))
            {
                viewAccessor.ReadArray<TData>(0, fetchedData, 0, arrayLength);
            }

            return fetchedData;
        }

        internal static void WriteDataArray<TData>(this MemoryMappedFile memoryMappedFile, int offset, TData[] arrayToWrite)
            where TData : struct
        {
            int dataSize = Marshal.SizeOf(typeof(TData));
            using (var viewAccessor = memoryMappedFile.CreateViewAccessor(offset, dataSize * arrayToWrite.Length))
            {
                viewAccessor.WriteArray<TData>(0, arrayToWrite, 0, arrayToWrite.Length);
            }
        }

        internal static IEnumerable<TData> ReadCollection<TData>(this MemoryMappedFile memoryMappedFile, int offset)
            where TData : struct
        {

            int dataSize = Marshal.SizeOf(typeof(TData));
            int countDataSize = Marshal.SizeOf(typeof(int));
            int collectionCount = memoryMappedFile.ReadData<int>(offset);
            var collectionData = memoryMappedFile.ReadDataArray<TData>(offset + countDataSize, collectionCount);
            return collectionData;
        }

        internal static void WriteCollection<TData>(this MemoryMappedFile memoryMappedFile, int offset, IEnumerable<TData> collectionToWrite)
            where TData : struct
        {
            int dataSize = Marshal.SizeOf(typeof(TData));
            int countDataSize = Marshal.SizeOf(typeof(int));
            int collectionCount = collectionToWrite.Count();
            memoryMappedFile.WriteData(offset, collectionCount);
            memoryMappedFile.WriteDataArray(offset + countDataSize, collectionToWrite.ToArray());
        }

    }
}
